﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace common {
	public class wav_op {
		/// <summary>
		/// clear the silence in front of recording
		/// </summary>
		/// <param name="audio"></param>
		/// <param name="n_audio"></param>
		public void clear_silence(string audio, out string n_audio) {
			singlechannelextract(audio, out audio, "left");
			MemoryStream mem_stream = null;
			FileStream fs = null;
			FileStream n_fs = null;
			BinaryReader binRead = null;

			n_audio = audio.Replace(".wav", "_tmp.wav");

			try {
				mem_stream = new MemoryStream();
				mem_stream.Write(new byte[44], 0, 44);

				fs = new FileStream(audio, FileMode.Open);
				binRead = new BinaryReader(fs);
				var _header_data = new byte[44];
				fs.Read(_header_data, 0, 44);
				var m_DataSize = fs.Length;
				var start_pst = 0;

				for (var i = 0; i < m_DataSize / 2; i++) {
					try {
						int data = binRead.ReadInt16();
						if (data > 2000) {
							start_pst = i;
							break;
						}
					} catch {
						break;
					}
				}
				var data_con = new byte[fs.Length - fs.Position];
				fs.Read(data_con, 0, data_con.Length);
				mem_stream.Write(data_con, 0, data_con.Length);

				var _header = new wav_header();

				var header = _header.get_header(_header_data);
				var wave_Header = _header.init_header((int)mem_stream.Length - 44, header.FMT_Channel, header.BitsPerSample, header.FMT_SamplesPerSec);
				var array2 = string_op.StructToBytes(wave_Header);
				mem_stream.Position = 0L;
				mem_stream.Write(array2, 0, array2.Length);
				mem_stream.Position = 0L;

				n_fs = new FileStream(n_audio, FileMode.Create);
				mem_stream.WriteTo(n_fs);
			} catch {
				throw;
			} finally {
				if (mem_stream != null)
					mem_stream.Dispose();
				if (binRead != null)
					binRead.Dispose();
				if (fs != null)
					fs.Dispose();
				if (n_fs != null)
					n_fs.Dispose();
				if (File.Exists(audio))
					File.Delete(audio);

			}

		}

		/// <summary>
		/// http download file
		/// </summary>
		/// <param name="url">download file url</param>
		/// <param name="path">the new file path (include file name)</param>
		/// <returns></returns>
		public bool HttpDownload(string url, string path) {
			try {
				var file_path = Path.GetDirectoryName(path);
				if (!Directory.Exists(file_path)) {
					Directory.CreateDirectory(file_path);
				}
				using (var fs = new FileStream(path, FileMode.CreateNew)) {
					var request = WebRequest.Create(url) as HttpWebRequest;
					HttpWebResponse response = null;
					var i = 0;
					while (i++ < 1) {
						try {
							response = request.GetResponse() as HttpWebResponse;
							break;
						} catch (WebException ex) {
							if (ex.Message.Contains("404"))
								throw new FileNotFoundException("recoding file is not exist", url);
						}
					}

					if (response == null)
						throw new WebException("get response error", WebExceptionStatus.UnknownError);

					var responseStream = response.GetResponseStream();
					var ms = new MemoryStream();
					var bArr = new byte[1024];
					var size = responseStream.Read(bArr, 0, bArr.Length);
					while (size > 0) {
						ms.Write(bArr, 0, size);
						size = responseStream.Read(bArr, 0, bArr.Length);
					}
					ms.WriteTo(fs);
					ms.Close();
					ms.Dispose();
					ms = null;
					responseStream.Close();
				}
				return true;
			} catch (Exception ex) {
				//Console.WriteLine(ex.StackTrace);
				throw ex;
			}
		}

		/// <summary>
		/// download http wav file
		/// </summary>
		/// <param name="url">wav url</param>
		/// <param name="file">the save path of local file, create the path if it doesn't exist</param>
		/// <param name="retry">retry connection times，default is 3</param>
		/// <param name="timeout"></param>
		/// <returns></returns>
		public async Task<bool> download_wav(string url, string file, int retry = 1, int timeout = 60) {
			var task_source = new TaskCompletionSource<bool>();
			var cancel_source = new CancellationTokenSource(60 * 1000);
			cancel_source.Token.Register(
				() => {
					task_source.TrySetException(new TimeoutException($"download file(url={url}) timeout(time={timeout})"));
				});

#pragma warning disable CS4014
			Task.Factory.StartNew(
				() => {
					HttpWebRequest request = null;
					HttpWebResponse response = null;
					MemoryStream ms = null;
					try {
						var file_path = Path.GetDirectoryName(file);

						if (!Directory.Exists(file_path))
							Directory.CreateDirectory(file_path);

						using (var fs = new FileStream(file, FileMode.CreateNew)) {
							request = WebRequest.Create(url) as HttpWebRequest;
							var i = 0;
							while (i++ < retry) {
								try {
									response = request.GetResponse() as HttpWebResponse;
									break;
								} catch (WebException ex) {
									if (ex.Message.Contains("404"))
										throw new FileNotFoundException("recoding file is not exist", url);
								}
							}

							if (response == null)
								throw new WebException("get response error", WebExceptionStatus.UnknownError);

							if (cancel_source.Token.IsCancellationRequested)
								return;

							var responseStream = response.GetResponseStream();
							ms = new MemoryStream();
							var bArr = new byte[1024];
							var size = responseStream.Read(bArr, 0, bArr.Length);
							while (size > 0) {
								ms.Write(bArr, 0, size);
								size = responseStream.Read(bArr, 0, bArr.Length);
							}
							ms.WriteTo(fs);
							ms.Close();
							ms.Dispose();
							ms = null;
							responseStream.Close();
						}
						task_source.TrySetResult(true);
					} catch (Exception ex) {
						if (cancel_source.Token.IsCancellationRequested)
							return;
						task_source.TrySetException(ex);
					} finally {
						if (ms != null) {
							ms.Close();
							ms.Dispose();
							ms = null;
						}
					}
				});
#pragma warning restore CS4014

			return await task_source.Task;
		}


		public async Task<byte[]> download_wav(string url, int retry = 1, int timeout = 60) {
			var task_source = new TaskCompletionSource<byte[]>();
			var cancel_source = new CancellationTokenSource(60 * 1000);
			cancel_source.Token.Register(
				() => {
					task_source.TrySetException(new TimeoutException($"download file(url={url}) timeout(time={timeout})"));
				});

			HttpWebRequest request = null;
			HttpWebResponse response = null;
			MemoryStream ms = null;
			byte[] buffer;
			try {

				request = WebRequest.Create(url) as HttpWebRequest;
				var i = 0;
				while (i++ < retry) {
					response = request.GetResponse() as HttpWebResponse;
					break;
				}

				if (response == null)
					throw new WebException("get response error", WebExceptionStatus.UnknownError);

				var responseStream = response.GetResponseStream();
				ms = new MemoryStream();
				var bArr = new byte[1024];
				var size = responseStream.Read(bArr, 0, bArr.Length);
				while (size > 0) {
					ms.Write(bArr, 0, size);
					size = responseStream.Read(bArr, 0, bArr.Length);
				}
				buffer = new byte[ms.Length];
				ms.Read(buffer, 0, buffer.Length);
				ms.Close();
				ms.Dispose();
				ms = null;
				responseStream.Close();
				task_source.TrySetResult(buffer);
			} catch (Exception ex) {
				task_source.TrySetException(ex);
			} finally {
				if (ms != null) {
					ms.Close();
					ms.Dispose();
					ms = null;
				}
			}

			return await task_source.Task;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="audio"></param>
		/// <param name="n_audio"></param>
		/// <param name="channel"></param>
		public void singlechannelextract(string audio, out string n_audio, string channel = "left") {

			n_audio = audio.Replace(".wav", $"_{(channel == "left" ? "L" : "R")}.wav");

			FileStream fs = null;
			FileStream n_fs = null;
			MemoryStream mem_stream = null;
			try {
				mem_stream = new MemoryStream();

				mem_stream.Write(new byte[44], 0, 44);

				fs = new FileStream(audio, FileMode.Open);
				var _header_data = new byte[44];
				fs.Read(_header_data, 0, 44);

				if (BitConverter.ToInt16(_header_data, 22) == 1) {
					n_audio = audio;
					return;
				}

				while (fs.Position != fs.Length) {
					var data = new byte[2];
					fs.Read(data, 0, 2);
					mem_stream.Write(data, 0, data.Length);
					fs.Read(data, 0, 2);
				}

				var _header = new wav_header();
				var header = _header.get_header(_header_data);

				var wave_Header = _header.init_header((int)mem_stream.Length - 44, 1, header.BitsPerSample, header.FMT_SamplesPerSec);
				var array2 = string_op.StructToBytes(wave_Header);
				mem_stream.Position = 0L;
				mem_stream.Write(array2, 0, array2.Length);
				mem_stream.Position = 0L;

				n_fs = new FileStream(n_audio, FileMode.Create);
				mem_stream.WriteTo(n_fs);
			} catch {
				throw;
			} finally {
				if (mem_stream != null) {
					mem_stream.Close();
					mem_stream.Dispose();
					mem_stream = null;
				}
				if (fs != null) {
					fs.Close();
					fs.Dispose();
					fs = null;
				}
				if (n_fs != null) {
					n_fs.Close();
					n_fs.Dispose();
					n_fs = null;
				}
				if (n_audio != audio && File.Exists(audio))
					File.Delete(audio);
			}

		}
	}
	public class wav_header {
		/// <summary>  
		/// 结构体初始化赋值  
		/// </summary>  
		/// <param name="data_len">文件总字节数，data+wav header</param>  
		/// <returns></returns>  
		public s_wav_header init_header(int data_len, ushort channels = 1, ushort bits = 16, int sample_rate = 8000) {
			return new s_wav_header {
				RIFF_ID = 0x46464952,
				File_Size = data_len - 8,
				RIFF_Type = 0x45564157,
				FMT_ID = 0x20746d66,
				FMT_Size = 0x10,
				FMT_Tag = 1,
				FMT_Channel = channels,
				FMT_SamplesPerSec = sample_rate,
				AvgBytesPerSec = channels * sample_rate * bits / 8,
				BlockAlign = (ushort)(channels * bits / 8),
				BitsPerSample = bits,
				DATA_ID = 0x61746164,
				DATA_Size = data_len - 44
			};
		}

		//public s_wav_header get_header(string wav_file) {

		//}

		public s_wav_header get_header(byte[] header_data) {
			var _header = new s_wav_header();
			if (header_data.Length != 44)
				return _header;


			_header.RIFF_ID = System.BitConverter.ToInt32(header_data, 0);

			_header.File_Size = System.BitConverter.ToInt32(header_data, 4);

			_header.RIFF_Type = System.BitConverter.ToInt32(header_data, 8);

			_header.FMT_ID = System.BitConverter.ToInt32(header_data, 12);

			_header.FMT_Size = System.BitConverter.ToInt32(header_data, 16);

			_header.FMT_Tag = System.BitConverter.ToInt16(header_data, 20);

			_header.FMT_Channel = System.BitConverter.ToUInt16(header_data, 22);

			_header.FMT_SamplesPerSec = System.BitConverter.ToInt32(header_data, 24);

			_header.AvgBytesPerSec = System.BitConverter.ToInt32(header_data, 28);

			_header.BlockAlign = System.BitConverter.ToUInt16(header_data, 32);

			_header.BitsPerSample = System.BitConverter.ToUInt16(header_data, 34);

			_header.DATA_ID = System.BitConverter.ToInt32(header_data, 36);

			_header.DATA_Size = System.BitConverter.ToInt32(header_data, 40);

			return _header;
		}
	}
	/// <summary>  
	/// 语音音频头  
	/// </summary>
	public struct s_wav_header {
		/// <summary>
		/// "RIFF"
		/// </summary>
		public int RIFF_ID;

		/// <summary>
		/// the totle number of bytes from the next filed to end of the file
		/// </summary>
		public int File_Size;

		/// <summary>
		/// "WAVE"
		/// </summary>
		public int RIFF_Type;

		/// <summary>
		/// "fmt"
		/// </summary>
		public int FMT_ID;

		/// <summary>
		/// "16","18","20","40"
		/// </summary>
		public int FMT_Size;

		/// <summary>
		/// "1"
		/// </summary>
		public short FMT_Tag;

		/// <summary>
		/// the number of channels 
		/// </summary>
		public ushort FMT_Channel;

		/// <summary>
		/// sample rate
		/// </summary>
		public int FMT_SamplesPerSec;

		/// <summary>
		/// the byte of sample in one second (channels×FMT_SamplesPerSec×BitsPerSample/8)
		/// </summary>
		public int AvgBytesPerSec;

		/// <summary>
		/// data block size (channels×BitsPerSample/8)
		/// </summary>
		public ushort BlockAlign;

		/// <summary>
		/// sample bits
		/// </summary>
		public ushort BitsPerSample;

		/// <summary>
		/// 
		/// </summary>
		public int DATA_ID;

		/// <summary>
		/// the size of data
		/// </summary>
		public int DATA_Size;
	}

}
